/*
 * BangoLogger.cpp
 *
 */
#include "BangoConstants.h"
#include "BangoLogger.h"


BangoLogger::BangoLogger(QObject *parent) : QObject(parent)
{
	_logEvents = BangoConstants::GetInstance()->SETTING_LOGEVENTS;
	_sessionResumePeriod = BangoConstants::GetInstance()->SETTING_SESSION_RESUME_PERIOD;
	_postURL = BangoConstants::GetInstance()->SETTING_POST_URL;
	_useOfflineStorage = BangoConstants::GetInstance()->SETTING_USEOFFLINESTORAGE;
	_postDelayed = false;
	_findLocation = BangoConstants::GetInstance()->SETTING_FINDLOCATION;
	_applicationType = "";
	_applicationChannel = "";
	_locationSource = BangoConstants::GetInstance()->SETTING_LOCATION_SOURCE;
	 qnam = new QNetworkAccessManager(this);

}

BangoLogger::~BangoLogger()
{

}

void BangoLogger::setQVariantData(const QVariant &data)
{
	_events = data;
}

void BangoLogger::setJsonData(const QString &data)
{
	if(_jsonString == data){
		return;
	}
	_jsonString = data;
}

void BangoLogger::GetLoggerProperties()
{
	_networkName = BangoConstants::GetInstance()->UNKNOWN_INFORMATION;
	_networkCountry = BangoConstants::GetInstance()->UNKNOWN_INFORMATION;
	_networkId = BangoConstants::GetInstance()->UNKNOWN_INFORMATION;
	_isRoaming = false;

	CellularNetworkInfo *cni = new CellularNetworkInfo();
	cni->setParent(this);
	if(cni)
	{
		_networkName = cni->name();
		_networkCountry = cni->mobileCountryCode();
		_networkId = cni->mobileNetworkCode();
		_isRoaming = cni->isRoaming();
	}

}

void BangoLogger::ClearEvents()
{
	QMutableVectorIterator<BangoEvent> iter(_bangoEvents);
	iter.toBack();
	while (iter.hasPrevious()) {
		iter.remove();
	}

	_bangoEvents.clear();
}

QString BangoLogger::GetApplicationVersion()
{
	QString appVersion = qApp->applicationVersion();
	return appVersion;
}
/**
 * Initialises the logging system. This should be called when starting the application.
 * and is normally done in the onStartSession call
 *
 * @param applicationId,
 * 			The application Id. This should be set up in Bango Management Tools prior to use
 */

void BangoLogger::InitLogger(QString applicationID)
{
	qDebug() << __PRETTY_FUNCTION__;
	_applicationId = applicationID;
	double locationLatitude = 0;
	double locationLongitude = 0;
	double locationAltitude = 0;
	float locationAccuracy = 0;

	QString simCardSerial;
	QString device;
	QString model;
	QString hardwareId;
	QString version;
	QString pin;

	HardwareInfo *hw = new HardwareInfo();
	SimCardInfo *sci = new SimCardInfo();
	hw->setParent(this);
	sci->setParent(this);

	if(hw) {
		device = hw->modelName();
		model = hw->modelNumber();
		version = BangoStatic::GetOperatingSystemVersion();
		hardwareId = hw->hardwareId();
		pin = hw->pin();
	}

	if(sci->state() == 5) {
		simCardSerial = sci->serialNumber();
	}

	if(_applicationVersion == "") {
		_applicationVersion = GetApplicationVersion();
	}
	if(_userId == "") {
		_userId = BangoStatic::GetUserID();
	}
	if(_userAgent == "") {
		_userAgent = BangoStatic::GetUserAgent();
	}

	if(_findLocation){
		QGeoPositionInfo geoPositionInfo = GetLocation();

		if(geoPositionInfo.isValid()){
			locationAccuracy = geoPositionInfo.attribute(QGeoPositionInfo::HorizontalAccuracy);
			locationLongitude = geoPositionInfo.coordinate().longitude();
			locationLatitude = geoPositionInfo.coordinate().latitude();
			locationAltitude = geoPositionInfo.coordinate().altitude();
		}
	}

	GetLoggerProperties();
	_bangoProfile.SetValues(BangoConstants::GetInstance()->SDK_TYPE, BangoConstants::GetInstance()->SDK_VERSION, _applicationId,
			_applicationType, _applicationChannel, _applicationVersion, _userId, _sessionResumePeriod,
			BangoStatic::GetConnectionType(), _isRoaming, _locationSource, locationAltitude, locationLatitude, locationLongitude, locationAccuracy, _networkId, _networkName, _networkCountry,
			_networkName, simCardSerial, device, model, hardwareId, version, pin, BangoStatic::GetOperatorConnectionType());
	_bangoEvents = QVector<BangoEvent>();


	if(_useOfflineStorage)
	{
		_bangoStorage.PersistDatabaseConnection(_persistDatabaseConnection);
		_bangoStorage.LogProfile(_bangoProfile);
	}


}

int BangoLogger::LogEvent(QString eventName, int errorLevel, QString eventValue, QString eventDetail, QString errorId, QMap<QString, QString> eventParameters)
{
	qDebug() << __PRETTY_FUNCTION__;
	int returnValue = -1;
	BangoEvent bangoEvent;

	    if (_logEvents)
	    {
	        QDateTime currentTime = QDateTime::currentDateTime();


	        QDateTime eventTime = currentTime.toUTC();
	        QDateTime eventLocalTime = currentTime.toLocalTime();

	        QVector<BangoNameValuePair> eventParametersVector;

	        if (eventParameters.size() > 0)
	        {
	            QMapIterator<QString, QString>iter(eventParameters);

	            while (iter.hasNext())
	            {
	                iter.next();
	                eventParametersVector.append(BangoNameValuePair(iter.key(), iter.value()));
	            }
	        }
	        bangoEvent.EventID(_bangoEvents.size());
	        bangoEvent.ConnectionType(BangoStatic::GetConnectionType());
	        bangoEvent.OperatorConnectionType(BangoStatic::GetOperatorConnectionType());
	        bangoEvent.EventName(eventName);
	        bangoEvent.EventValue(eventValue);
	        bangoEvent.EventDetail(eventDetail);
	        bangoEvent.EventTime(eventTime);
	        bangoEvent.EventLocalTime(eventLocalTime);
	        bangoEvent.ErrorLevel(errorLevel);
	        bangoEvent.ErrorID(errorId);
	        bangoEvent.EventParameters(eventParametersVector);
	       _bangoEvents.append(bangoEvent);

	       if(_useOfflineStorage) {
	    	   returnValue = _bangoStorage.LogEvent(bangoEvent);
	    	   if(_maximumEventsPerProfile > 0 && _bangoEvents.size() >= _maximumEventsPerProfile) {
	    		   _bangoStorage.LogProfile(_bangoProfile);
	    		   _bangoEvents = QVector<BangoEvent>();
	    	   }
	       }
	       else{

	    	   returnValue = _bangoEvents.size();
	       }
	    }
	    return returnValue;
}

int BangoLogger::LogEvent(QString eventName, QString eventValue, QString eventDetail, QMap<QString,QString> eventParameters)
{
	return LogEvent(eventName, BangoConstants::GetInstance()->ERROR_NONE, eventValue, eventDetail, "", eventParameters);
}

int BangoLogger::LogEvent(QString eventName, QString eventValue, QString eventDetail)
{
	return LogEvent(eventName, BangoConstants::GetInstance()->ERROR_NONE, eventValue, eventDetail, "",  QMap<QString,QString>());
}

int BangoLogger::LogEventStart(QString eventValue, QString eventDetail, QMap<QString,QString> eventParameters)
{
    return LogEvent(BangoSessionEvents::SESSION_START, eventValue, eventDetail, eventParameters);
}

int BangoLogger::LogEventEnd(QString eventValue, QString eventDetail, QMap<QString,QString> eventParameters)
{
    return LogEvent(BangoSessionEvents::SESSION_END, eventValue, eventDetail, eventParameters);
}

int BangoLogger::LogEventError(QString errorId, QString errorClass, QString message, QMap<QString,QString> eventParameters)
{
    return LogEvent(BangoSessionEvents::EVENT_ERROR, BangoConstants::GetInstance()->ERROR_USER, errorClass, message, errorId, eventParameters);
}

int BangoLogger::LogInternalErrorCritical(exception& ex)
{
    return LogInternalError(ex, BangoConstants::GetInstance()->ERROR_INTERNAL_CRITICAL);
}

int BangoLogger::LogInternalErrorWarning(exception& ex)
{
    return LogInternalError(ex, BangoConstants::GetInstance()->ERROR_INTERNAL_WARNING);
}

int BangoLogger::LogInternalError(exception& ex, int errorLevel)
{
	return LogEvent("", errorLevel, ex.what(), "", "", QMap<QString, QString>());
}

void BangoLogger::writeJson(bool notCurrentProfile)
{
	qDebug() << __PRETTY_FUNCTION__;
	JsonDataAccess jda;
	QString jsonBuffer;
	BangoProfile bangoProfile;
	QVariantList eventsList;
	QVector<BangoEvent> bangoEvents;
	QVector<BangoProfile> profiles = _bangoStorage.GetProfilesForPost(notCurrentProfile);
	QVariant eventData;
	QVectorIterator<BangoProfile> iter(profiles);
	while(iter.hasNext()){
		bangoProfile = iter.next();
		if(bangoProfile.ProfileID() == _bangoStorage.ProfileId()){
			bangoEvents = _bangoStorage.GetEventsForPost(bangoProfile.ProfileID());
			QVariantMap profile = bangoProfile.GetProfileDetails();
			if(bangoEvents.size() > 0){
				QVectorIterator<BangoEvent> iter(bangoEvents);
				while(iter.hasNext()){
					BangoEvent bEvent = iter.next();

					eventsList.append(bEvent.GetEventDetails());
				}
			}
			profile.insert("Events", eventsList);
			eventData = QVariant(QVariantList() << QVariant(profile));
		}
	}
	setQVariantData(eventData);
	jda.saveToBuffer(_events, &jsonBuffer);
	setJsonData(QString());
	if(jda.hasError()){
		const DataAccessError err = jda.error();
		const QString errorMsg = tr("Error writing Json: %1").arg(err.errorMessage());
		qDebug() << errorMsg;
	}
	else {
		setJsonData(jsonBuffer);
	}
}

QString BangoLogger::GetJSONString(BangoProfile bangoProfile, QVector<BangoEvent> bangoEvents)
{
	qDebug() << __PRETTY_FUNCTION__;
	JsonDataAccess jda;
	QVariantMap postProfile = bangoProfile.GetProfileDetails();
	QVariantList postEvents;
	QString returnString;

	if(bangoEvents.size() > 0) {
		QVectorIterator<BangoEvent> iter(bangoEvents);

		while(iter.hasNext()) {
			BangoEvent bEvent = (BangoEvent) iter.next();
			if(!bEvent.EventPosted()) {
				postEvents.append(bEvent.GetEventDetails());
			}
		}
	}

	postProfile.insert("Events", postEvents);
	QVariant events = QVariant(postProfile);

	jda.saveToBuffer(events, &returnString);
	if(jda.hasError()) {
		const DataAccessError err = jda.error();
		const QString errorMessage = tr("Error writing Json: %1").arg(err.errorMessage());
		qDebug() << errorMessage;
	}
	qDebug() << returnString;
	return returnString;
}



bool BangoLogger::PostEvents(bool notCurrentProfile, bool closeDatabase)
{
	qDebug() << __PRETTY_FUNCTION__;
	bool returnValue = false;
	QVector<BangoProfile> bangoProfiles;
	BangoProfile bangoProfile;
	QVector<BangoEvent> appEvents;

	if(_useOfflineStorage)
	{
		bangoProfiles = _bangoStorage.GetProfilesForPost(notCurrentProfile);
		QVectorIterator<BangoProfile> iter(bangoProfiles);
		while(iter.hasNext()){
			bangoProfile = iter.next();
			appEvents = _bangoStorage.GetEventsForPost(bangoProfile.ProfileID());
			if(appEvents.size() > 0){
				returnValue = PostEvents(bangoProfile, appEvents);
			}
			else{
				if(bangoProfile.ProfileID() != _bangoStorage.ProfileId()){
					qDebug() << "Deleting posted events";
					_bangoStorage.DeleteEventsPosted(bangoProfile.ProfileID());
				}
			}
		}
		if(closeDatabase){
			CloseDatabase();
		}
	}
	else{
		bangoProfile = _bangoProfile;
		QList<BangoEvent> eventsList = _bangoEvents.toList();
		appEvents = eventsList.toVector();
		_bangoEvents.clear();
		returnValue = PostEvents(bangoProfile, appEvents);
		if(!returnValue){
			_bangoEvents = appEvents;
		}
	}
	return returnValue;
}

void BangoLogger::onReplyFinished()
{
	qDebug() << __PRETTY_FUNCTION__;
	QNetworkReply *reply = qobject_cast<QNetworkReply*>(this->sender());
	int responseCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
	qDebug() << QString::number(responseCode);
	if(reply){
		if(reply->error() == QNetworkReply::NoError){

				QString response = QString::fromUtf8(reply->readAll());
					qDebug() << response;
					int profileId = reply->property("profileId").toInt();
					qDebug() << "Updating events posted for profileID : " << QString::number(profileId);
					_bangoStorage.UpdateEventsPosted(profileId);

			reply->deleteLater();
		}
	}
	else {
		qDebug() << "Upload Failed";
	}
}

bool BangoLogger::PostEvents(BangoProfile bangoProfile, QVector<BangoEvent> appEvents)
{
	qDebug() << __PRETTY_FUNCTION__;
	QString responseMessage;
	bool isWifi = BangoStatic::IsWiFiConnection();
	QString JSONString = GetJSONString(bangoProfile, appEvents);
	qDebug() << JSONString;
	if(_logEvents && (!_isRoaming || isWifi)){

		QByteArray dataToSend(JSONString.toAscii());
		qDebug() << dataToSend.length();
		QNetworkRequest postRequest;
		QNetworkReply *reply;

		postRequest.setUrl(QUrl(_postURL));
		if(UserAgent() != "") {
			postRequest.setRawHeader("User-Agent", UserAgent().toAscii());
		}

		postRequest.setHeader(QNetworkRequest::ContentLengthHeader, dataToSend.length());
		postRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
		postRequest.setRawHeader("Connection", "close");

		reply = qnam->post(postRequest, dataToSend);
		reply->setProperty("profileId", QVariant(QString::number(bangoProfile.ProfileID())));
		connect(reply, SIGNAL(finished()), this, SLOT(onReplyFinished()));
	}
	return true;
}

QString BangoLogger::ViewBatch()
{

	writeJson(false);
	qDebug() << _jsonString;
	return _jsonString;
}

//Location permissions must be granted from within the bar descriptor file of the application
//in order to utilise these functions


QGeoPositionInfo BangoLogger::GetLocation()
{
	qDebug() << __PRETTY_FUNCTION__;
	QGeoPositionInfo geoPositionInfo;
	if(_findLocation && !_locationDataSource)
	{
		_locationDataSource = QGeoPositionInfoSource::createDefaultSource(this);
		if(_locationSource == BangoConstants::GetInstance()->POSITIONINGMETHOD_ALLPOSITIONINGMETHODS || _locationSource == BangoConstants::GetInstance()->POSITIONINGMETHOD_NONSATELLITEPOSITION) {

				if(_locationSource == BangoConstants::GetInstance()->POSITIONINGMETHOD_ALLPOSITIONINGMETHODS) {

					_locationDataSource->setPreferredPositioningMethods(QGeoPositionInfoSource::AllPositioningMethods);
				}
				else {

					_locationDataSource->setPreferredPositioningMethods(QGeoPositionInfoSource::NonSatellitePositioningMethods);
				}
			}
			else {

				_locationDataSource->setPreferredPositioningMethods(QGeoPositionInfoSource::SatellitePositioningMethods);
			}
		geoPositionInfo = _locationDataSource->lastKnownPosition(true);
		connect(_locationDataSource, SIGNAL(positionUpdated(QGeoPositionInfo)), this, SLOT(positionUpdated(QGeoPositionInfo)));
		_locationDataSource->startUpdates();

		return geoPositionInfo;

	}
	return geoPositionInfo;
}

void BangoLogger::positionUpdated(QGeoPositionInfo geoPositionInfo)
{
	if(geoPositionInfo.isValid()) {
		_locationDataSource->stopUpdates();
		_bangoProfile.UpdateLocationValues(geoPositionInfo.coordinate().altitude(), geoPositionInfo.coordinate().latitude(), geoPositionInfo.coordinate().longitude(), geoPositionInfo.attribute(QGeoPositionInfo::HorizontalAccuracy));
	}
}

void BangoLogger::EndLocationUpdates()
{
	if(_findLocation && _locationDataSource)
	{
		_locationDataSource->stopUpdates();
	}
}

void BangoLogger::CloseDatabase()
{
	_bangoStorage.CloseDatabase(true);
}
